//C++ header - Open Scene Graph - Copyright (C) 1998-2002 Robert Osfield
//Distributed under the terms of the GNU Library General Public License (LGPL)
//as published by the Free Software Foundation.

// -*-c++-*-

#ifndef OSG_TEXTURE
#define OSG_TEXTURE 1

#include <osg/GL>
#include <osg/Types>
#include <osg/Image>
#include <osg/StateAttribute>
#include <osg/ref_ptr>
#include <osg/Vec4>

#include <vector>
#include <map>
#include <set>

// if not defined by gl.h use the definition found in:
// http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_filter_anisotropic.txt
#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
#endif

#ifndef GL_ARB_texture_compression
#define GL_COMPRESSED_ALPHA_ARB           0x84E9
#define GL_COMPRESSED_LUMINANCE_ARB       0x84EA
#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB
#define GL_COMPRESSED_INTENSITY_ARB       0x84EC
#define GL_COMPRESSED_RGB_ARB             0x84ED
#define GL_COMPRESSED_RGBA_ARB            0x84EE
#define GL_TEXTURE_COMPRESSION_HINT_ARB   0x84EF
#define GL_TEXTURE_IMAGE_SIZE_ARB         0x86A0
#define GL_TEXTURE_COMPRESSED_ARB         0x86A1
#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2
#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3
#endif

#ifndef GL_EXT_texture_compression_s3tc
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT   0x83F0
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT  0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT  0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT  0x83F3
#endif

#ifndef GL_MIRRORED_REPEAT_IBM
#define GL_MIRRORED_REPEAT_IBM            0x8370
#endif

#ifndef GL_CLAMP_TO_EDGE
#define GL_CLAMP_TO_EDGE                  0x812F
#endif

#ifndef GL_CLAMP_TO_BORDER_ARB
#define GL_CLAMP_TO_BORDER_ARB            0x812D
#endif

#ifndef GL_GENERATE_MIPMAP_SGIS
#define GL_GENERATE_MIPMAP_SGIS           0x8191
#define GL_GENERATE_MIPMAP_HINT_SGIS      0x8192
#endif

#ifndef GL_TEXTURE_3D
#define GL_TEXTURE_3D                     0x806F
#endif

namespace osg {

/** Texture state class which encapsulates OpenGl texture functionality.*/
class SG_EXPORT Texture : public StateAttribute
{

    public :
        
        Texture();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        Texture(const Texture& text,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            StateAttribute(text,copyop),
            _handleList(),
            _modifiedTag(),
            _image(copyop(text._image.get())),
            _target(text._target),
            _wrap_s(text._wrap_s),
            _wrap_t(text._wrap_t),
            _wrap_r(text._wrap_r),
            _min_filter(text._min_filter),
            _mag_filter(text._mag_filter),
            _texParamtersDirty(false),
            _internalFormatMode(text._internalFormatMode),
            _internalFormatValue(text._internalFormatValue),
            _borderColor(text._borderColor),
            _textureWidth(text._textureWidth),
            _textureHeight(text._textureHeight),
            _subloadMode(text._subloadMode),
            _subloadOffsX(text._subloadOffsX),
            _subloadOffsY(text._subloadOffsY),
            _subloadWidth(text._subloadWidth),
            _subloadHeight(text._subloadHeight) {}

        META_StateAttribute(osg, Texture,TEXTURE);
        
        virtual bool isTextureAttribute() const { return true; }

        /** return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.*/
        virtual int compare(const StateAttribute& rhs) const;

        virtual void getAssociatedModes(std::vector<GLMode>& modes) const 
        {
            modes.push_back(_target);
        }

        /** Set the texture image. */
        void setImage(Image* image);

        /** Get the texture image. */
        Image* getImage() { return _image.get(); }

        /** Get the const texture image. */
        inline const Image* getImage() const { return _image.get(); }

        /** Copy pixels into a 2D texture image.As per glCopyTexImage2D.
          * Creates an OpenGL texture object from the current OpenGL background
          * framebuffer contents at pos \a x, \a y with width \a width and
          * height \a height. \a width and \a height must be a power of two.
          */
        void copyTexImage2D(State& state, int x, int y, int width, int height );

        /** Copy a two-dimensional texture subimage. As per glCopyTexSubImage2D.
          * Updates portion of an existing OpenGL texture object from the current OpenGL background
          * framebuffer contents at pos \a x, \a y with width \a width and
          * height \a height. \a width and \a height must be a power of two,
          * and writing into the texture with offset \a xoffset and \a yoffset.
          */
        void copyTexSubImage2D(State& state, int xoffset, int yoffset, int x, int y, int width, int height );

        enum WrapParameter {
            WRAP_S,
            WRAP_T,
            WRAP_R
        };

        enum WrapMode {
            CLAMP  = GL_CLAMP,
            CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE,
            CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER_ARB,
            REPEAT = GL_REPEAT,
            MIRROR = GL_MIRRORED_REPEAT_IBM
        };

        /** Set the texture wrap mode.*/
        void setWrap(const WrapParameter which, const WrapMode wrap);
        /** Get the texture wrap mode.*/
        const WrapMode getWrap(const WrapParameter which) const;


        /** Sets the border color for this texture. Makes difference only if
          * wrap mode is CLAMP_TO_BORDER */
        void setBorderColor(const Vec4& color) { _borderColor = color; _texParamtersDirty = true; }
        
        const Vec4& borderColor(void) const { return _borderColor; }
        

        enum FilterParameter {
            MIN_FILTER,
            MAG_FILTER
        };

        enum FilterMode {
            LINEAR                    = GL_LINEAR,
            LINEAR_MIPMAP_LINEAR      = GL_LINEAR_MIPMAP_LINEAR,
            LINEAR_MIPMAP_NEAREST     = GL_LINEAR_MIPMAP_NEAREST,
            NEAREST                   = GL_NEAREST,
            NEAREST_MIPMAP_LINEAR     = GL_NEAREST_MIPMAP_LINEAR,
            NEAREST_MIPMAP_NEAREST    = GL_NEAREST_MIPMAP_NEAREST,
            ANISOTROPIC               = GL_TEXTURE_MAX_ANISOTROPY_EXT
        };


        /** Set the texture filter mode.*/
        void setFilter(const FilterParameter which, const FilterMode filter);

        /** Get the texture filter mode.*/
        const FilterMode getFilter(const FilterParameter which) const;


        enum InternalFormatMode {
            USE_IMAGE_DATA_FORMAT,
            USE_USER_DEFINED_FORMAT,
            USE_ARB_COMPRESSION,
            USE_S3TC_DXT1_COMPRESSION,
            USE_S3TC_DXT3_COMPRESSION,
            USE_S3TC_DXT5_COMPRESSION
        };

        /** Set the internal format mode.
          * Note, If the mode is set USE_IMAGE_DATA_FORMAT, USE_ARB_COMPRESSION,
          * USE_S3TC_COMPRESSION the internalFormat is automatically selected,
          * and will overwrite the previous _internalFormatValue.
          */
        inline void setInternalFormatMode(const InternalFormatMode mode) { _internalFormatMode = mode; }

        /** Get the internal format mode.*/
        inline const InternalFormatMode getInternalFormatMode() const { return _internalFormatMode; }

        /** Set the internal format to use when creating OpenGL textures.
          * Also sets the internalFormatMode to USE_USER_DEFINED_FORMAT.
          */
        inline void setInternalFormatValue(const int internalFormat)
        {
            _internalFormatMode = USE_USER_DEFINED_FORMAT;
            _internalFormatValue = internalFormat;
        }

        /** Get the internal format to use when creating OpenGL textures.*/
        inline const int getInternalFormatValue() const { return _internalFormatValue; }
        
        /** return the OpenGL texture object for specified context.*/
        inline const uint getTextureObject(const uint contextID) const { if (contextID<_handleList.size()) return _handleList[contextID]; else return 0;}


        enum SubloadMode {
            OFF,
            AUTO,
            IF_DIRTY
        };

        /** Set the texture subload mode. */
        inline void setSubloadMode(const SubloadMode mode) { _subloadMode = mode; }

        /** Get the texture subload mode. */
        inline const SubloadMode getSubloadMode() const { return _subloadMode; }

        /** Set the texture subload offsets. */
        inline void setSubloadOffset(const int x, const int y) {
            _subloadOffsX = x;
            _subloadOffsY = y;
        }

        /** Get the texture subload offsets. */
        inline void getSubloadOffset(int& x, int& y) const {
            x = _subloadOffsX;
            y = _subloadOffsY;
        }
        
        /** Set the texture subload width. If width or height are zero then
          * the repsective size value is calculated from the source image sizes. */
        inline void setSubloadSize(const int width, const int height) {
            _subloadWidth = width;
            _subloadHeight = height;
        }

        /** Get the texture subload width. */
        inline void getSubloadSize(int& width, int& height) const {
            width = _subloadWidth;
            height = _subloadHeight;
        }

        /** Get the handle to the texture object for the current context.*/
        inline GLuint& getHandle(const uint contextID) const
        {
            // pad out handle list if required.
            if (_handleList.size()<=contextID)
                _handleList.resize(contextID+1,0);

            // get the globj for the current contextID.
            return _handleList[contextID];
        }

        inline uint& getModifiedTag(const uint contextID) const
        {
            // pad out handle list if required.
            if (_modifiedTag.size()<=contextID)
                _modifiedTag.resize(contextID,0);

            // get the modified tag for the current contextID.
            return _modifiedTag[contextID];
        }

        /** Force a recompile on next apply() of associated OpenGL texture objects.*/
        void dirtyTextureObject();

        /** On first apply (unless already compiled), create the minmapped 
          * texture and bind it, subsequent apply will simple bind to texture.*/
        virtual void apply(State& state) const;
        
        /** Compile the texture mip maps.  Implemented by simply calling apply().*/
        virtual void compile(State& state) const;





        /** use deleteTextureObject instead of glDeleteTextures to allow
          * OpenGL texture objects to cached until they can be deleted
          * by the OpenGL context in which they were created, specified
          * by contextID.*/
        static void deleteTextureObject(uint contextID,GLuint handle);

        /** flush all the cached display list which need to be deleted
          * in the OpenGL context related to contextID.*/
        static void flushDeletedTextureObjects(uint contextID);
        

    protected :

        virtual ~Texture();


        /** Method which does setting of texture paramters. */
        void applyTexParameters(GLenum target, State& state) const;

        /** Method which does the creation of the texture itself, and
          * does not set or use texture binding. */
        virtual void applyTexImage(GLenum target, Image* image, State& state) const;

        typedef std::vector<GLuint> TextureNameList;
        mutable TextureNameList _handleList;

        typedef std::vector<uint> ImageModifiedTag;
        mutable ImageModifiedTag _modifiedTag;


        // not ideal that _image is mutable, but its required since
        // Image::ensureDimensionsArePowerOfTwo() can only be called
        // in a valid OpenGL context, a therefore within an Texture::apply
        // which is const...
        mutable ref_ptr<Image> _image;

        GLenum _target; // defaults to GL_TEXTURE_2D

        WrapMode _wrap_s;
        WrapMode _wrap_t;
        WrapMode _wrap_r;

        FilterMode _min_filter;
        FilterMode _mag_filter;

        // true if apply tex parameters required. 
        mutable bool _texParamtersDirty;


        InternalFormatMode  _internalFormatMode;
        int                 _internalFormatValue;

        Vec4 _borderColor;

        // subloaded images can have different texture and image sizes.
        mutable GLsizei _textureWidth, _textureHeight;

        SubloadMode _subloadMode;
        GLint _subloadOffsX, _subloadOffsY;
        GLsizei _subloadWidth, _subloadHeight;

        // static cache of deleted display lists which can only 
        // by completely deleted once the appropriate OpenGL context
        // is set.
        typedef std::map<uint,std::set<uint> > DeletedTextureObjectCache;
        static DeletedTextureObjectCache s_deletedTextureObjectCache;

};

}

#endif
